#ifndef ns_mscoop_h
#define ns_mscoop_h

#include <string.h>
#include "agent.h"
#include "tclcl.h"
#include "packet.h"
#include "address.h"
#include "ip.h"
#include <agent.h>
#include <packet.h>
#include <trace.h>
#include <timer-handler.h>
#include <random.h>
#include <classifier-port.h>
#include "mscoop_pkt.h"
#include "CircularBuff.h"
#include "Histograma.h"
#include "StorageAssignment.h"
#include "Tupla.h"
#include "Graph.h"
#include "Sensor_Info.h"
#include "RoutingTable.h"
#include "MsgCounters.h"
#include "Matrix.h"
#include "GenericRow.h"
#include "TimestampControl.h"
#include "MultiSum.h"
#include "/usr/include/postgresql/libpq-fe.h"
#include <fstream>
#include <vector>
#include <map>
#include <iostream>
#include <utility>
#include <functional>
#include <numeric>

using namespace std;

#define CURRENT_TIME         Scheduler::instance().clock()
#define JITTER               (Random::uniform()*3)
#define JITTER_TIMERS        (Random::uniform()*3)
#define JITTER_SUM           (Random::uniform()*3)
//Jitter é uma variação estatística do atraso na entrega de dados em uma rede,
//ou seja, pode ser definida como a medida de variação do atraso entre os pacotes sucessivos de dados.
#define MAX_V 30
#define TAM 5; 

/* Variáveis Globais*/
std::vector<double> blankRow;
std::vector< vector <double> > matriz_dados;
Graph grafo; // Grafo utilizado para calcular os caminhos mais curtos entre sensores
RoutingTable* tabelaR; // Tabela de Roteamento 
MsgCounters totalCounters; //Contador de msgs Global
Matrix mtx_prob, mtx_cost_prod, mtx_cost_query, mtx_custos, mtx_consultas;
std::vector<double> tempos;
int ctemp;

class MScoop;

/* Timers */

enum Timer {
    DADOS,
    SUMARIOS,
    SAS,
    CONSULTAS,
    PIGGY,
    ALL
};

/* Tipo de dados a gerar*/
enum DataType {
    REAL,
    UNIQUE,
    EQUAL,
    RANDOM,
    GAUSSIAN,
    SIMILAR
};

//Tipo de Scoop - Calculo da SA

enum SaCalcType {
    SIMPLES,
    DUPLAS
};

//Tipo de distribuição de dados do scoop

enum SaIntervalType {
    PLAIN,
    EQUIDEPTH
};

enum CounterType {
    OWN,
    FORWARD
};



typedef void(MScoop::*firefunction)(void); //Definição do tipo de apontador para uma função

class MScoop_Timer : public TimerHandler {
public:

    MScoop_Timer(MScoop* agent, firefunction f) : TimerHandler() {
        agent_ = agent;
        firing_ = f;
    }
protected:
    MScoop* agent_;
    virtual void expire(Event* e);
    firefunction firing_;
};

/* Final Timer*/

/* Agent */

class MScoop : public Agent {
    /* Friends */
    friend class MScoop_Timer;

    nsaddr_t ra_addr_;
    int ponto_acesso_;
    // TIMERS
    MScoop_Timer timer_dados_; // Timer para geração de dados do sensor
    MScoop_Timer timer_sumarios_; // Timer para envio das mensagens de sumario
    MScoop_Timer timer_sa_; // Timer para geração da Storage Assigment
    MScoop_Timer timer_consultas_; // Timer para realização de consultas
    MScoop_Timer timer_piggy_; // Timer para realização de consultas
    //Binds OTCl - C++ 
    int num_sensores_; //Numero de sensores
    double time_dados_; //Tempo base para envio de dados        
    double time_sumarios_; //Tempo fixo para envio de sumários
    double time_sa_; //Tempo fixo para geração da SA
    double time_consultas_; //Tempo base para envio de sumários
    double time_piggy_; //Tempo base para envio de sumários
    int tipo_dados_; //Tipo de geração de dados do sensor
    int tipo_scoop_; //Tipo de cálculo da SA
    int tipo_dist_sa_; //Tipo de distribuição da SA
    int size_buffer_; //Tamanho do buffer de dados
    int histo_nBins_; // Número de bins para o histograma
    int sa_nBins_; //Número de bins para a SA    
    double max_piggy_;
    double const_piggy_;
    int perc_con_duplas_;
    double radio_w_range_;
    double threshold_data_;
    double threshold_histo_;
    //Sensor DB
    int freq_producao_; //frequencia produção de dados do sensor
    int freq_consulta_; //frequencia consulta     
    bool piggy;
    MultiSum temp_piggy_data;
    float last_avg_data;
    float last_avg_histo;
    CircularBuff* dados_sensor_;
    vector<int> descendants;
    float ** matriz_custos_;
    int ** matriz_consultas_;
    MsgCounters myCounters; //Contador de msgs especifico de cada sensor
    //DB Ponto de Acesso
    StorageAssignment atualStorageAssignment_;
    int sa_id_; //Sequencia para o id da SA
    double time_interval_SA_;
    int total_consultas_;
    int count_queries;
    map <int, Histograma> bd_histogramas_; // BD do Ponto de acesso com os ultimos histogramas
    map <int, StorageAssignment> indices; // Índices antigos        
    int accessible_var_;
    u_int8_t seq_num_;
    int count_dados_;
    int indice_dados;





private:
    void gerar_dados();
public:
    MScoop(nsaddr_t);
    int id; // número de identificação

    virtual int command(int argc, const char*const* argv);
    virtual void recv(Packet*, Handler*);
    // FUNÇÕES PARA TRATAR TIMERS
    void init_timers();
    void init_matriz_cons(int, int);
    void init_matriz_costs(int, int);
    void print_matriz_costs(int, int);
    void reset_mscoop_timer(int);
    void calc_pairs(StorageAssignment &);
    void menor_custo(std::vector< Tupla >&, int valor, int valor2, int rows);
    int sum_cons(int);
    pair<int, float> menor_cost(int);
    pair<int, float> menor_cost_j(int, int);
    vector<int> getSensores();
    void firing_timer_dados();
    void firing_timer_sumarios();
    void firing_timer_sa();
    void firing_timer_consultas();
    void firing_timer_piggybacking();
    float prob_gerar(int32_t, float);
    float prob_gerar(int32_t, GenericRow, float);
    float prob_gerar(int32_t, GenericRow);
    float prob_user_queries(int);
    void enviar_msg(Handler*, Packet*, double, int);
    void send_mscoop_pkt_sumario();
    void create_and_send_sa();
    void create_and_send_sa_duplas();
    void create_and_send_query();
    void send_mscoop_pkt_SA(StorageAssignment);
    void send_mscoop_pkt_dados(float);
    void send_mscoop_pkt_multi_sumarios(MultiSum);
    pair<float, float> calculate_limits();
    void calc_PProd(vector<GenericRow>, vector<int>, float);
    int calc_EQUIIntervals(GenericRow&, vector<int>, float, float);
    void calc_SA();
    Packet* create_pkt(int destino, int next_hop, int msgType);
protected:


    int mscoopPrivFunc(Packet*, int);

    inline nsaddr_t& ra_addr() {
        return ra_addr_;
    }
};

////////////////////////////////////////////////////
///// Auxiliar FUNCTIONS  /////
////////////////////////////////////////////////////

void StringExplode(string str, string separator, vector<string>* results) {
    int found;
    found = str.find_first_of(separator);
    while (found != (int) string::npos) {
        if (found > 0) {
            results->push_back(str.substr(0, found));
        }
        str = str.substr(found + 1);
        found = str.find_first_of(separator);
    }
    if (str.length() > 0) {
        results->push_back(str);
    }
}

void carregar_dados_reais(int num_sensores_) {
    string strOneLine;
    ifstream myFile("/home/lusitanio/Documentos/ns-allinone-2.34/ns-2.34/svn/rnscoop/full_data.txt");
    int sensor, leituras = 0;
    double valor;
    vector<string> r;
    puts("INICIO DO CARREGAMENTO DOS DADOS REAIS");
    if (myFile.is_open()) {
        while (myFile) {

            getline(myFile, strOneLine);
            StringExplode(strOneLine, ";", &r);
            //printf("Tamanho: %d \n",r.size());
            if (r.size() > 2) {
                sensor = atoi(r.at(0).c_str());
                valor = atof(r.at(2).c_str());
                matriz_dados[sensor].push_back(valor);
                leituras++;
                //printf("Matriz[%d][%d]: %f\n", sensor,matriz_dados[sensor].size(), valor);
            }
            r.clear();
        }
        myFile.close();
    } else
        cout << "Unable to open file";

    printf("FIM DO CARREGAMENTO DOS DADOS REAIS -> LEITURAS CARREGADAS: %d\n", leituras);
}

void carregar_dados_largaescala(int num_sensores_) {
    string strOneLine, aux;
    ifstream myFile("/home/lusitanio/Documentos/ns-allinone-2.34/ns-2.34/svn/rnscoop/cenario_dados_500.txt");
    int sensor = 0;
    int leituras = 0;
    double valor;
    vector<string> r;
    puts("INICIO DO CARREGAMENTO DOS DADOS REAIS");
    vector<string>::iterator it;
    if (myFile.is_open()) {
        while (myFile) {

            getline(myFile, strOneLine);
            StringExplode(strOneLine, ";", &r);
            printf("Tamanho[%d]: %d \n", sensor, r.size());
            if (sensor < num_sensores_) {
                for (it = r.begin(); it < r.end(); it++) {
                    aux = *it;
                    valor = atof(aux.c_str());
                    matriz_dados[sensor].push_back(valor);
                    //printf("Matriz[%d][%d]: %f\n", sensor,matriz_dados[sensor].size(), valor);
                }
            }
            sensor++;
            r.clear();
        }
        myFile.close();
    } else
        cout << "Unable to open file";

    printf("FIM DO CARREGAMENTO DOS DADOS LARGA ESCALA \n");
}

void carregar_sensores(int num_sensores_, double range, int ponto_acesso) {
    string strOneLine;
    ifstream myFile("/home/lusitanio/Documentos/ns-allinone-2.34/ns-2.34/svn/rnscoop/local_sensores.txt");
    //ifstream myFile("/home/lusitanio/Documentos/ns-allinone-2.34/ns-2.34/svn/rnscoop/local_sensor_500.txt");
    int leituras = 0, id;
    float xx, yy;
    vector<string> r;
    std::vector<Sensor_Info> info;
    puts("INICIO DO CARREGAMENTO DOS SENSORES");
    if (myFile.is_open()) {
        while (myFile) {
            //grafo = init_graph(num_sensores_);
            getline(myFile, strOneLine);
            StringExplode(strOneLine, " ", &r);
            //printf("Tamanho: %d \n",r.size());
            if (r.size() > 2) {
                id = atoi(r.at(0).c_str());
                xx = atof(r.at(1).c_str());
                yy = atof(r.at(2).c_str());
                Sensor_Info aux = *(new Sensor_Info(id, xx, yy));
                aux.print();
                info.push_back(aux);
                leituras++;
                //            for (int i=0;i<3;i++){
                //                printf("STRING[%d]: %s \n",i,r.at(i).c_str());
                //            }
                //printf("Matriz[%d][%d]: %f\n", sensor,matriz_dados[sensor].size(), valor);
            }
            r.clear();
        }
        myFile.close();
    } else
        cout << "Unable to open file";

    printf("FIM DO CARREGAMENTO DOS SENSORES -> SENSORES CARREGADOS: %d\n", leituras);
    grafo = *new Graph(num_sensores_);

    tabelaR = new RoutingTable(grafo, info, range, ponto_acesso);

}

int getHops(const int o, const int d) {
    return (tabelaR->getRoute(o, d)).second;
}


int hdr_mscoop_pkt::offset_;

int roundup(float d) {
    return floor(d + 0.5);
}
#endif // ns_mscoop_h
